﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Styx;
using Styx.CommonBot;
using Styx.TreeSharp;
using Styx.WoWInternals;
using Styx.WoWInternals.WoWObjects;
using Action = Styx.TreeSharp.Action;

namespace TheFinalCall
{
    /*
     * Apply Aura: Mod Rating (33554432) Value: 5
     * Apply Aura: Mod Stat - % (Strength, Agility, Intellect) Value: 5
     * Apply Aura: 
     */

    /// <summary>
    ///     indicates buff category an aura belongs to.  values must be a unique bit to allow creating
    ///     masks to represent a single aura that provides buffs in multiple categories, such as
    ///     Arcane Brilliance being PartyBuff.Spellpower+PartyBuff.Crit
    /// </summary>
    public enum PartyBuffType
    {
        // from http://www.wowhead.com/guide=1100
        None = 0,
        Stats = 1, // Mark of the Wild, Legacy of the Emperor, Blessing of Kings, Embrace of the Shale Spider
        Stamina = 1 << 1, // PW:Fortitude, Imp: Blood Pact, Commanding Shout, Qiraji Fortitude
        AttackPower = 1 << 2, // Horn of Winter, Trueshot Aura, Battle Shout
        SpellPower = 1 << 3, // Arcane Brilliance, Dalaran Brilliance, Burning Wrath, Dark Intent, Still Water
        Haste = 1 << 4, // Unholy Aura, Swiftblade's Cunning, Unleashed Rage, Crackling Howl, Serpent's Swiftness
        SpellHaste = 1 << 5, // Moonkin Aura, Shadowform, Elemental Oath, Mind Quickening
        Crit = 1 << 6,
        // Leader of the Pack, Arcane Brilliance, Dalaran Brilliance, Legacy of the White Tiger, Bellowing Roar, Furious Howl, Terrifying Roar, Fearless Roar, Still Water
        Mastery = 1 << 7 // Blessing of Might, Grace of Air, Roar of Courage, Spirit Beast Blessing
    }


    public static class PartyBuff
    {
        private static readonly Dictionary<string, PartyBuffType> dictBuffs = new Dictionary<string, PartyBuffType>
            {
                {"Mark of the Wild", PartyBuffType.Stats},
                {"Legacy of the Emperor", PartyBuffType.Stats},
                {"Blessing of Kings", PartyBuffType.Stats},
                {"Embrace of the Shale Spider", PartyBuffType.Stats},
                {"Power Word: Fortitude", PartyBuffType.Stamina},
                {"Imp: Blood Pact", PartyBuffType.Stamina},
                {"Commanding Shout", PartyBuffType.Stamina},
                {"Qiraji Fortitude", PartyBuffType.Stamina},
                {"Horn of Winter", PartyBuffType.AttackPower},
                {"Trueshot Aura", PartyBuffType.AttackPower},
                {"Battle Shout", PartyBuffType.AttackPower},
                {"Arcane Brilliance", PartyBuffType.SpellPower | PartyBuffType.Crit},
                {"Dalaran Brilliance", PartyBuffType.SpellPower | PartyBuffType.Crit},
                {"Burning Wrath", PartyBuffType.SpellPower},
                {"Dark Intent", PartyBuffType.SpellPower},
                {"Still Water", PartyBuffType.SpellPower | PartyBuffType.Crit},
                {"Unholy Aura", PartyBuffType.Haste},
                {"Swiftblade's Cunning", PartyBuffType.Haste},
                {"Unleashed Rage", PartyBuffType.Haste},
                {"Crackling Howl", PartyBuffType.Haste},
                {"Serpent's Swiftness", PartyBuffType.Haste},
                {"Moonkin Aura", PartyBuffType.SpellHaste},
                {"Shadowform", PartyBuffType.SpellHaste},
                {"Elemental Oath", PartyBuffType.SpellHaste},
                {"Mind Quickening", PartyBuffType.SpellHaste},
                {"Leader of the Pack", PartyBuffType.Crit},
                // { "Arcane Brilliance",               new BuffCategory[] {PartyBuff.SpellPower | PartyBuff.Crit}, // here for doc only... see SpellPower section
                // { "Dalaran Brilliance",              new BuffCategory[] {PartyBuff.SpellPower | PartyBuff.Crit}, // here for doc only... see SpellPower section
                {"Legacy of the White Tiger", PartyBuffType.Crit},
                {"Bellowing Roar", PartyBuffType.Crit},
                {"Furious Howl", PartyBuffType.Crit},
                {"Terrifying Roar", PartyBuffType.Crit},
                {"Fearless Roar", PartyBuffType.Crit},
                // { "Still Water",                     new BuffCategory[] {PartyBuff.SpellPower | PartyBuff.Crit}, // here for doc only... see SpellPower section

                {"Blessing of Might", PartyBuffType.Mastery},
                {"Grace of Air", PartyBuffType.Mastery},
                {"Roar of Courage", PartyBuffType.Mastery},
                {"Spirit Beast Blessing", PartyBuffType.Mastery},
            };

        /// <summary>
        ///     time next PartyBuff attempt allowed
        /// </summary>
        public static DateTime timeAllowBuff = DateTime.Now;

        /// <summary>
        ///     minimum TimeSpan to wait between PartyBuff casts
        /// </summary>
        public static TimeSpan spanBuffFrequency = new TimeSpan(0, 0, 30);

        private static int _secsBeforeBattle;

        /// <summary>
        ///     # of seconds this character waits prior to start of battleground
        ///     to do PartyBuff casts.  if 0 when retrieving value, will initialize
        ///     it to a random value between 5 and 12.  to force it to recalc a
        ///     new random value, set to 0 perodically
        /// </summary>
        public static int secsBeforeBattle
        {
            get
            {
                if (_secsBeforeBattle == 0)
                    _secsBeforeBattle = new Random().Next(5, 12);

                return _secsBeforeBattle;
            }

            set { _secsBeforeBattle = value; }
        }

        /// <summary>
        ///     true: we have a Bloodlust-like buff, typically indicating we should cast
        ///     other long cooldown abilities that we save for such an occassion
        /// </summary>
        public static bool WeHaveBloodlust
        {
            get
            {
                return StyxWoW.Me.HasAnyAura(StyxWoW.Me.IsHorde ? "Bloodlust" : "Heroism", "Time Warp",
                                             "Ancient Hysteria");
            }
        }

        /// <summary>
        ///     maps a Spell name to its associated PartyBuff vlaue
        /// </summary>
        /// <param name="name">spell name</param>
        /// <returns>PartyBuff enum mask if exists for spell, otherwise PartyBuff.None</returns>
        public static PartyBuffType GetPartyBuffForSpell(string name)
        {
            PartyBuffType bc;
            if (!dictBuffs.TryGetValue(name, out bc))
                bc = PartyBuffType.None;

            return bc;
        }

        /// <summary>
        ///     check a WoWUnit for a particular PartyBuff enum
        /// </summary>
        /// <param name="unit">unit to check for buff</param>
        /// <param name="cat">buff to check for.  may be a mask of multiple buffs if any will do, such as PartyBuff.Stats + PartyBuff.Mastery</param>
        /// <returns>true if any buff matching the mask in 'cat' is found, otherwise false</returns>
        public static bool HasPartyBuff(this WoWUnit unit, PartyBuffType cat)
        {
            foreach (WoWAura a in unit.GetAllAuras())
            {
                PartyBuffType bc = GetPartyBuffForSpell(a.Name);
                if ((bc & cat) != PartyBuffType.None)
                    return true;
            }
            return false;
        }

        /// <summary>
        /// </summary>
        /// <param name="unit"></param>
        /// <param name="name"></param>
        /// <returns></returns>
        public static bool HasPartyBuff(this WoWUnit unit, string name)
        {
            PartyBuffType cat = GetPartyBuffForSpell(name);
            foreach (WoWAura a in unit.GetAllAuras())
            {
                PartyBuffType bc = GetPartyBuffForSpell(a.Name);
                if ((bc & cat) != PartyBuffType.None)
                    return true;
            }
            return false;
        }

        /// <summary>
        ///     gets a PartyBuff mask representing all categories of buffs present
        /// </summary>
        /// <param name="unit">WoWUnit to check for buffs</param>
        /// <returns>PartyBuff mask representing all buffs founds</returns>
        public static PartyBuffType GetPartyBuffs(this WoWUnit unit)
        {
            var buffs = PartyBuffType.None;
            foreach (WoWAura a in unit.GetAllAuras())
            {
                PartyBuffType bc = GetPartyBuffForSpell(a.Name);
                buffs |= bc;
            }
            return buffs;
        }

        /// <summary>
        ///     gets a PartyBuff mask representing all categories of buffs missing
        /// </summary>
        /// <param name="unit">WoWUnit to check for missing buffs</param>
        /// <returns>PartyBuff mask representing all buffs that are missing</returns>
        public static PartyBuffType GetMissingPartyBuffs(this WoWUnit unit)
        {
            PartyBuffType buffMask = PartyBuffType.Stats | PartyBuffType.Stamina | PartyBuffType.AttackPower |
                                     PartyBuffType.SpellPower | PartyBuffType.Haste | PartyBuffType.SpellHaste |
                                     PartyBuffType.Crit | PartyBuffType.Mastery;
            PartyBuffType buffs = GetPartyBuffs(unit);
            buffs = (~buffs) & buffMask;
            return buffs;
        }


        /// <summary>
        ///     sets blessings timer to wait 'spanBuffFrequency' before next allowed cast
        ///     ... allowed blessing attempt
        /// </summary>
        public static void ResetReadyToPartyBuffTimer()
        {
            timeAllowBuff = DateTime.Now + spanBuffFrequency;
        }

        /// <summary>
        ///     returns true if has been atleast 1 minute since last blessing attempt.
        ///     .. if in battlegrounds will also check that the battle only has
        ///     .. random # seconds (5 to 12) remaining or has already begun
        /// </summary>
        /// <returns></returns>
        public static bool IsItTimeToBuff()
        {
            if (!StyxWoW.Me.IsInGroup())
                return true;

            if (DateTime.Now < timeAllowBuff)
                return false;

            if (!Battlegrounds.IsInsideBattleground)
                return true;

            return DateTime.Now > (Battlegrounds.BattlefieldStartTime - new TimeSpan(0, 0, secsBeforeBattle));
        }

        /// <summary>
        ///     private function to buff individual units.  makes assumptions
        ///     that certain states have been checked previously. only the
        ///     group interface to PartyBuffs is exposed since it handles the
        ///     case of LocalPlayer not in a group as well
        /// </summary>
        /// <param name="name">spell name</param>
        /// <param name="onUnit">unit selection delegate</param>
        /// <param name="requirements">requirements delegate that must be true to cast buff</param>
        /// <param name="myMutexBuffs">list of buffs your mechanics make mutually exclusive for you to cast.  For example, BuffGroup("Blessing of Kings", ret => true, "Blessing of Might") </param>
        /// <returns></returns>
        private static Composite BuffUnit(string name, UnitSelectionDelegate onUnit, SimpleBooleanDelegate requirements,
                                          params string[] myMutexBuffs)
        {
            return
                new Decorator(
                    ret => onUnit(ret) != null
                           && (PartyBuffType.None != (onUnit(ret).GetMissingPartyBuffs() & GetPartyBuffForSpell(name)))
                           &&
                           (myMutexBuffs == null || myMutexBuffs.Count() == 0 ||
                            !onUnit(ret)
                                 .GetAllAuras()
                                 .Any(a => a.CreatorGuid == StyxWoW.Me.Guid && myMutexBuffs.Contains(a.Name))),
                    new Sequence(
                        Spell.Buff(name, onUnit, requirements),
                        new Action(ret =>
                            {
                                Debug.Assert(PartyBuffType.None != GetPartyBuffForSpell(name));
                                if (PartyBuffType.None != GetPartyBuffForSpell(name))
                                    ResetReadyToPartyBuffTimer();
                                else
                                    Logger.WriteDebug("Programmer Error: should use Spell.Buff(\"{0}\") instead", name);
                            })
                        )
                    );
        }

        /// <summary>
        ///     checks group members in range if they have a buff providing the benefits
        ///     that this spell does, and if not casts the buff upon them.  understands
        ///     similar buffs such as Blessing of Kings being same as Mark of the Wild.
        ///     if  not in a group, will treat Me as a group of one
        ///     Will not buff if Mounted unless during prep phase of a battleground
        /// </summary>
        /// <param name="name">spell name of buff</param>
        /// <param name="myMutexBuffs">list of your buffs which are mutually exclusive to 'name'.  For example, BuffGroup("Blessing of Kings", ret => true, "Blessing of Might") </param>
        /// <returns></returns>
        public static Composite BuffGroup(string name, params string[] myMutexBuffs)
        {
            return BuffGroup(name, ret => true, myMutexBuffs);
        }

        /// <summary>
        ///     checks group members in range if they have a buff providing the benefits
        ///     that this spell does, and if not casts the buff upon them.  understands
        ///     similar buffs such as Blessing of Kings being same as Mark of the Wild.
        ///     if  not in a group, will treat Me as a group of one.
        ///     Will not buff if Mounted unless during prep phase of a battleground
        /// </summary>
        /// <param name="name">spell name of buff</param>
        /// <param name="requirements">requirements delegate that must be true for cast to occur</param>
        /// <param name="myMutexBuffs">list of your buffs which are mutually exclusive to 'name'.  For example, BuffGroup("Blessing of Kings", ret => true, "Blessing of Might") </param>
        /// <returns></returns>
        public static Composite BuffGroup(string name, SimpleBooleanDelegate requirements, params string[] myMutexBuffs)
        {
            return
                new Decorator(ret => IsItTimeToBuff()
                                     && SpellManager.HasSpell(name)
                                     &&
                                     (!StyxWoW.Me.Mounted ||
                                      (Battlegrounds.IsInsideBattleground &&
                                       DateTime.Now < Battlegrounds.BattlefieldStartTime)),
                              new PrioritySelector(
                                  ctx =>
                                  Unit.GroupMembers.FirstOrDefault(
                                      m =>
                                      m.IsAlive && m.DistanceSqr < 30*30 &&
                                      (PartyBuffType.None != (m.GetMissingPartyBuffs() & GetPartyBuffForSpell(name)))),
                                  BuffUnit(name, ctx => (WoWUnit) ctx, requirements, myMutexBuffs)
                                  )
                    );
        }
    }
}